package myicesscheduler.hibernate;

import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.*;
import org.hibernate.cfg.*;

/**
 * HibernateUtil contains functions to manage the session factory 
 */
class HibernateUtil
{
   private static final String WEBAPPS = "webapps"; // tomcat application installed folder name
   protected static final Log logger = LogFactory.getLog( HibernateUtil.class );

   /**
    * This is not designed to be an instance.
    */
   private HibernateUtil()
   {}

   /**
    * The session factory
    */
   private static SessionFactory sessionFactory;
   private static Configuration cfg;
   private static boolean bRunning_ = false;

   static
   {
      startDB();
   }

   synchronized static void startDB()
   {
      if( bRunning_ ) throw new RuntimeException( "Database is already running." );

      logger.info( "HibernateUtil::Singleton start" );

      try
      {
         // Create the SessionFactory from hibernate.cfg.xml
         cfg = new Configuration().configure();

         final String url = cfg.getProperty( "hibernate.connection.url" );
         if( null == url )
            throw new RuntimeException( "hibernate.connection.url property is not found." );

         if( url.contains( "${catalina.base}" ) )
         {
            final String catalBase = findCatalinaBase();
            if( null != catalBase )
            {
               logger.info( "Replacing a keyword ${catalina.base} from: " + url );
               final String resolvedURL = url.replaceFirst( "\\$\\{catalina.base\\}", catalBase );
               cfg.setProperty( "hibernate.connection.url", resolvedURL );
            }
         }

         logger.info( "Currect working directory: " + System.getProperty( "user.dir" ) );
         logger.info( "hibernate.connection.url: " + cfg.getProperty( "hibernate.connection.url" ) );
         sessionFactory = cfg.buildSessionFactory();
      } catch( Throwable ex )
      {
         // Make sure you log the exception, as it might be swallowed
         System.err.println( "Initial SessionFactory creation failed: " + ex );
         throw new ExceptionInInitializerError( ex );
      }

      logger.info( "Hibernate singleton is successfully created." );
      bRunning_ = true;
   }

   private static String findCatalinaBase()
   {
      final String catalinaBaseProperty = System.getProperty( "catalina.base" );
      if( null != catalinaBaseProperty )
      {
         final File fh = new File( catalinaBaseProperty );
         if( null != fh && fh.exists() && fh.isDirectory() )
         {
            logger.info( "property catalina.base is found: " + catalinaBaseProperty );
            return catalinaBaseProperty;
         }
      }

      final String classPath = System.getProperty( "java.class.path", "." );
      final String sep = String.valueOf( File.pathSeparatorChar );

      final String[] classPaths = Pattern.compile( String.valueOf( sep ) ).split( classPath );
      for( final String cp : classPaths )
      { // cp will be "(catalina.base)/webapps/myWebApplicationName/WEB-INF/classes
         final File fileWebInf = new File( cp ).getParentFile();
         if( null == fileWebInf ) continue;
         if( false == fileWebInf.getName().equals( "WEB-INF" ) ) continue;

         final String catalinaBase = fileWebInf.getParentFile().getParentFile().getParentFile()
               .getAbsolutePath();
         final File webapps = new File( catalinaBase + File.separator + WEBAPPS );
         if( false == webapps.exists() ) continue;

         return catalinaBase;
      }

      logger.warn( "catalina.base path cannot be resolved." );
      return null;
   }

   /**
    * Returns the session factory
    * @return the session factory
    */
   public static SessionFactory getSessionFactory()
   {
      return sessionFactory;
   }

   public static SQLException doTransaction( IHibernateTransaction trans )
   {
      final Session session = HibernateUtil.getSessionFactory().openSession();
      session.beginTransaction();

      SQLException rst = null;
      try
      {
         trans.transaction( session );
         session.getTransaction().commit();
      } catch( SQLException e )
      {
         e.printStackTrace();
         rst = e;
      } finally
      {
         session.close();
      }
      return rst;
   }

   public static int getUniqueId( Session session, String tableName, String fieldNameOfPrimaryKey )
   {
      final Integer maxId = (Integer) session.createQuery(
            "SELECT MAX( " + fieldNameOfPrimaryKey + " ) FROM " + tableName ).uniqueResult();

      if( null == maxId ) return 0;
      return maxId + 1;
   }

   @SuppressWarnings( "unchecked" )
   public static < E > List< E > getList( Class< E > tableClass, final Map< String, String > where )
   {
      String SQL = "FROM " + tableClass.getSimpleName() + " WHERE";

      boolean bFirst = true;
      for( final String key : where.keySet() )
      {
         SQL += ( bFirst ? " " : " AND " ) + key + " = ?";
         bFirst = false;
      }

      final String FullSQL = SQL;
      final List< E > rst[] = new ArrayList[] { null };

      HibernateUtil.doTransaction( new IHibernateTransaction()
      {
         public void transaction( Session session ) throws SQLException
         {
            Query query = session.createQuery( FullSQL );

            int idx = 0;
            for( final String key : where.keySet() )
               query = query.setString( idx++, where.get( key ) );

            rst[ 0 ] = query.list();
         }
      } );

      return rst[ 0 ];
   }

   public static void delete( final Object obj )
   {
      doTransaction( new IHibernateTransaction()
      {
         public void transaction( Session session ) throws SQLException
         {
            session.delete( obj );
         }
      } );
   }

   synchronized public static void shutdown()
   {
      if( false == bRunning_ ) throw new RuntimeException( "Database is not running." );

      logger.info( "HibernateUtil::Shutdown" );
      doTransaction( new IHibernateTransaction()
      {
         public void transaction( Session session ) throws SQLException
         {
            session.createSQLQuery( "SHUTDOWN" ).executeUpdate();
         }
      } );
      sessionFactory.close();
      bRunning_ = false;
   }

   synchronized public static void restart()
   {
      shutdown();
      startDB();
   }

   synchronized public static boolean isRunning()
   {
      return bRunning_;
   }

   public static String getAbsoluteDatabaseFolder()
   {
      String folder = null;
      {
         final String url = cfg.getProperty( "hibernate.connection.url" );
         if( null == url )
            throw new RuntimeException( "hibernate.connection.url property is not found." );

         final int beginofFolder = url.lastIndexOf( ":" ) + 1;
         if( 0 == beginofFolder )
            throw new RuntimeException( "Database folder name cannot be resolved." );

         int endofFolder = url.lastIndexOf( "/" );
         if( -1 == endofFolder ) endofFolder = url.lastIndexOf( "\\" );
         folder = url.substring( beginofFolder, endofFolder );

         final File folderFH = new File( folder );
         if( folderFH.isDirectory() )
         {
            try
            {
               folder = folderFH.getCanonicalPath();
               //logger.info( "Database cannonical path: " + folder );
            } catch( IOException e )
            {
               e.printStackTrace();
               folder = folderFH.getAbsolutePath();
               //logger.info( "Database absolute path: " + folder );
            }
         }
      }

      if( !folder.contains( "${catalina.base}" ) ) return folder;

      final String catalBase = findCatalinaBase();
      if( null == catalBase )
         throw new RuntimeException( "${catalina.base} cannot be resolved: " + folder );

      final String resolvedFolder = folder.replaceFirst( "$\\{catalina.base\\}", catalBase );
      logger.info( "Hibernate absolute database folder: " + resolvedFolder );
      return resolvedFolder;
   }

   public static String getDatabaseName()
   {
      final String url = cfg.getProperty( "hibernate.connection.url" );
      if( null == url )
         throw new RuntimeException( "hibernate.connection.url property is not found." );

      int endofFolder = url.lastIndexOf( "/" );
      if( -1 == endofFolder ) endofFolder = url.lastIndexOf( "\\" );
      if( -1 == endofFolder ) throw new RuntimeException( "Database name cannot be resolved." );
      final int beginofFile = endofFolder + 1;

      final int endofFile = url.lastIndexOf( ";" );
      final String filename = ( -1 == endofFile ) ? url.substring( beginofFile ) : url.substring(
            beginofFile, endofFile );
      logger.info( "Hibernate database name: " + filename );
      return filename;
   }
}
